/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.business;

import java.util.*;
import ch.simpleel.*;
import ch.simpleel.accessors.*;
import ch.simpleel.internal.*;
import ch.simpleel.util.*;

/**
 * Implementation of {@link IPropertyResolver} were the user defines the mapping by calling {@link #addPropertyMapping(String, Class, String)} and
 * {@link #addELMapping(String, Class, String)}.<br>
 * Consider using a resolver like {@link SimpleResolver} which may be exchanged with a {@link MappingResolver}.
 * 
 * @author M. Hautle
 */
public class StaticResolver implements IPropertyResolver {
    /** {@link IValueAccessor}s mapped on property keys. The keys used in this map were built by {@link #buildKey(String, Class)}. */
    private final Map<Object, IValueAccessor> properties = new HashMap<Object, IValueAccessor>();

    /**
     * Adds a mapping from the given key to the specified property.
     * 
     * @param key The key
     * @param type The type on which the mapping gets applied
     * @param accessor The accessor
     * @param override True if an existing definition should be overriden
     * @return True if the mapping was added
     */
    public boolean addAccessorMapping(String key, Class type, IValueAccessor accessor, boolean override) {
        final Object pk = buildKey(key, type);
        if (!override && properties.containsKey(pk))
            return false;
        properties.put(pk, accessor);
        return true;
    }

    /**
     * Adds a mapping from the given key to the specified property.<br>
     * This call overrides an allready existing definition.
     * 
     * @param key The key
     * @param type The type on which the mapping gets applied
     * @param accessor The accessor
     * @return True if the mapping was added
     */
    public boolean addAccessorMapping(String key, Class type, IValueAccessor accessor) {
        return addAccessorMapping(key, type, accessor, true);
    }

    /**
     * Adds a mapping from the given key to the specified property.<br>
     * This call overrides an already existing definition.
     * 
     * @param key The key
     * @param src The property holder
     * @param property The property name
     * @param override True if an existing definition should be overridden
     * @return True if the mapping was added
     */
    public boolean addPropertyMapping(String key, Class src, String property, boolean override) {
        return addAccessorMapping(key, src, ValueAccessorManager.getPropertyAccessor(src, property), override);
    }

    /**
     * Adds a mapping from the given key to the specified property.<br>
     * This call overrides an allready existing definition.
     * 
     * @param key The key
     * @param src The property holder
     * @param property The property name
     */
    public void addPropertyMapping(String key, Class src, String property) {
        addPropertyMapping(key, src, property, true);
    }

    /**
     * Adds a mapping from the given key to the specified EL expression.<br>
     * This call overrides an allready existing definition.
     * 
     * @param key The key
     * @param type The type on which the mapping gets applied
     * @param el The EL expression
     * @param override True if an existing definition should be overriden
     * @return True if the mapping was added
     */
    @SuppressWarnings("unchecked")
    public boolean addELMapping(String key, Class type, String el, boolean override) {
        return addAccessorMapping(key, type, new ELAccessor(el, Object.class), override);
    }

    /**
     * Adds a mapping from the given key to the specified EL expression.<br>
     * This call overrides an allready existing definition.
     * 
     * @param key The key
     * @param type The type on which the mapping gets applied
     * @param el The EL expression
     */
    public void addELMapping(String key, Class type, String el) {
        addELMapping(key, type, el, true);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @SuppressWarnings("unchecked")
    public <S> S get(String key, Object bean, S fallback) {
        final IValueAccessor accessor = properties.get(buildKey(key, bean.getClass()));
        return accessor != null ? (S) accessor.getValue(bean, fallback) : fallback;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getPath(String key, Class type) {
        return properties.get(buildKey(key, type)).getPath();
    }

    /**
     * Retrives the accessor for the given key.
     * 
     * @param key The key
     * @return The accessor or null
     */
    protected IValueAccessor getAccessor(Object key) {
        assert key instanceof PropertyKey : "Key must be a PropertyKey!";
        return properties.get(key);
    }

    /**
     * Creates a unique key for the given parameters.<br>
     * Use this method to build the keys for {@link #properties}.
     * 
     * @param key The property key
     * @param type The type on which the key gets applied
     * @return A 'unique' key for the given information
     */
    protected final Object buildKey(String key, Class type) {
        return new PropertyKey(type, key);
    }
}
